package com.allesblinkt.mucom.client;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;

import com.allesblinkt.mucom.common.ClientItem;
import com.allesblinkt.mucom.common.Constants;
import com.allesblinkt.mucom.common.ClientRequest;
import com.allesblinkt.mucom.common.ContentItem;
import com.allesblinkt.mucom.common.ServerResponse;
import com.allesblinkt.mucom.common.ClientRequest.RequestType;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * The NetController is a separate Thread responsible for making requests to the Server and keeping the clients 
 * basic state
 * 
 * @author mys
 */
public class NetController implements Runnable, Constants {

	private Logger logger;

	private Thread runner;

	private ClientItem client;

	private Vector<ClientItem> clients;
	
	private XStream xstream;

	private boolean running = true;

	private JmDNS jmdns;

	private String serverUrl = null;

	private ClientState clientState = ClientState.INACTIVE;
	
	private Vector<UploadItem> uploadQueue;
	
	private Vector<ContentItem> takeQueue;

	private long lastTickle;


	public enum ClientState {
		ENTERING, ACTIVE, LEAVING, INACTIVE
	}

	public NetController( ClientItem theClient) {

		logger = Logger.getLogger(CLIENT_LOGGER);
		
		uploadQueue = new Vector<UploadItem>();
		takeQueue = new Vector<ContentItem>();

		
		clients = new Vector<ClientItem>();

		xstream = new XStream(new DomDriver());

		xstream.autodetectAnnotations(true);
		xstream.alias("response", ServerResponse.class);

		client = theClient;

		tickle();

		
		runner = new Thread(this);
		runner.setDaemon(true);
		runner.start();

		startServiceDiscovery();
		

	}
	
	

	
	
	private synchronized void handleUpload(){
		logger.log(Level.INFO, "Making upload request to server");

		ClientRequest request = new ClientRequest(client);

		request.setType(RequestType.UPLOAD);
		
		
		


		try {
			
			

			UploadItem theUploadItem  = uploadQueue.get(0);
			
			

			uploadQueue.remove(theUploadItem);
			
			
			URL url;
			URLConnection urlConn;
			DataOutputStream printout;
			// URL of CGI-Bin script.
			url = new URL(serverUrl);
			
			System.out.println(serverUrl);
			// URL connection channel.
			urlConn = url.openConnection();
			// Let the run-time system (RTS) know that we want input.
			urlConn.setDoInput(true);
			// Let the RTS know that we want to do output.
			urlConn.setDoOutput(true);
			// No caching, we want the real thing.
			urlConn.setUseCaches(false);
			// Specify the content type.


			urlConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=dill");





			DataInputStream fis = new DataInputStream(new BufferedInputStream(new FileInputStream(theUploadItem.file)));
			byte[] theData = new byte[(int) theUploadItem.file.length( )];

			fis.readFully(theData);
			fis.close();

			DataOutputStream raw = new DataOutputStream(urlConn.getOutputStream());
			Writer wr = new OutputStreamWriter(raw);

			
			String contentHeader =
				"--dill\r\n"
				+ "Content-Disposition: form-data; name=\"request\" \r\n"
				+ "Content-Type: text/xml\r\n"
				+ "\r\n";
			
			
			
			
			String fileHeader =
				"\r\n--dill\r\n"
				+ "Content-Disposition: form-data; name=\"upload\"; filename=\""
				+ theUploadItem.filename + "\"\r\n"
				+ "Content-Type: text/xml\r\n"
				+ "\r\n";
			
			
			String content = xstream.toXML(request);


			String multipartTrail = "\r\n--dill--\r\n";


			wr.write(contentHeader);

			wr.flush();
			wr.write(content);
			wr.flush( );
	
			
			
			wr.write(fileHeader);

			wr.flush();
			raw.write(theData);
			raw.flush( );
			wr.write(multipartTrail);
			wr.flush( );


			/* Read here */

			wr.close();
			raw.close();

			


			Object responseObject = xstream.fromXML(urlConn.getInputStream());

			if (responseObject instanceof ServerResponse) {
				ServerResponse serverResponse = (ServerResponse) responseObject;
				System.out.println("Got object back");
				System.out.println(serverResponse.getType());

				if (serverResponse.getType() == ServerResponse.ResponseType.ACTIVE) {
					if (clientState == ClientState.INACTIVE) {
						clientState = ClientState.ENTERING;
					}

					if (clientState == ClientState.LEAVING) {
						clientState = ClientState.ENTERING;
					}

				}

				if (serverResponse.getType() == ServerResponse.ResponseType.IDLE) {
					if (clientState == ClientState.LEAVING) {
						clientState = ClientState.INACTIVE;
					}
				}
			}

		} catch (Exception e) {
			logger.log(Level.WARNING, "Server request failed \nReason: "
					+ e.toString());

		}
	}
	
	private synchronized void handleStatusCommunication() {
		logger.log(Level.INFO, "Making request to server");

		ClientRequest request = new ClientRequest(client);
		
		request.addTakenItems(takeQueue);
		

		if (clientState == ClientState.LEAVING) {
			request.setType(RequestType.HANDOVER);
		}

		if (clientState == ClientState.ACTIVE) {
			request.setType(RequestType.RUNNING);
		}

		if (clientState == ClientState.ENTERING) {
			request.setType(RequestType.RUNNING);
		}

		if (clientState == ClientState.INACTIVE) {
			request.setType(RequestType.IDLE);
		}

		try {
			URL url;
			URLConnection urlConn;
			DataOutputStream printout;
			// URL of CGI-Bin script.
			url = new URL(serverUrl);
			// URL connection channel.
			urlConn = url.openConnection();
			// Let the run-time system (RTS) know that we want input.
			urlConn.setDoInput(true);
			// Let the RTS know that we want to do output.
			urlConn.setDoOutput(true);
			// No caching, we want the real thing.
			urlConn.setUseCaches(false);
			// Specify the content type.
			urlConn.setRequestProperty("Content-Type", CONTENT_TYPE);
			// Send POST output.
			printout = new DataOutputStream(urlConn.getOutputStream());

			String content = xstream.toXML(request);

			System.out.println(content);

			printout.writeBytes(content);
			printout.flush();
			printout.close();

			Object responseObject = xstream.fromXML(urlConn.getInputStream());

			if (responseObject instanceof ServerResponse) {
				ServerResponse serverResponse = (ServerResponse) responseObject;
				System.out.println("Got object back");
				System.out.println(serverResponse.getType());

				if (serverResponse.getType() == ServerResponse.ResponseType.ACTIVE) {
					if (clientState == ClientState.INACTIVE) {
						clientState = ClientState.ENTERING;
						
						clients.clear();
						
						clients.addAll(serverResponse.getClients());
						
						
					}

					if (clientState == ClientState.LEAVING) {
						clientState = ClientState.ENTERING;
					}

				}

				if (serverResponse.getType() == ServerResponse.ResponseType.IDLE) {
					if (clientState == ClientState.LEAVING) {
						clientState = ClientState.INACTIVE;
					}
					
					if(clientState == ClientState.ACTIVE){
						clientState = ClientState.LEAVING;
					}
				}
			}

		} catch (Exception e) {
			logger.log(Level.WARNING, "Server request failed \nReason: "
					+ e.toString());

		}

	}

	private void startServiceDiscovery() {

		try {
			jmdns = JmDNS.create();
			jmdns.addServiceListener(SERVICE_NAME, new MucomServiceListener());

			jmdns.list(SERVICE_NAME); // Somehow this is needed to trigger
										// resolv?!

		} catch (Exception e) {
			System.out.println(e);
		}

	}

	private void stopServiceDiscovery() {

		try {
			jmdns.close();
		} catch (Exception e) {
		}

	}

	private class MucomServiceListener implements ServiceListener {

		public void serviceAdded(ServiceEvent theEvent) {
			System.out.println("service added " + theEvent.getType() + " "
					+ theEvent.getName());
			theEvent.getDNS().requestServiceInfo(theEvent.getType(),
					theEvent.getName());
		}

		public void serviceRemoved(ServiceEvent theEvent) {
			System.out.println("service removed ");

		}

		public void serviceResolved(ServiceEvent theEvent) {
			System.out.println("service resolved ");
			System.out.println(theEvent.getInfo().getURL());

			if (serverUrl == null) {
				serverUrl = theEvent.getInfo().getURL();
			}
		}
	}
	
	public void tickle(){
		 lastTickle = System.currentTimeMillis();
	 
	}

	public void run() {
		while (running) {

			if (serverUrl != null) {
				handleStatusCommunication();
				
				if(hasFilesToUpload()){
					handleUpload();
				}
				
			
			}
			
			if(System.currentTimeMillis() - lastTickle > TICKLE_TIMEOUT * 1000){
				running = false;
			}

			try {
				Thread.sleep(CLIENT_POLL_INTERVALL * 1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		

		System.exit(0);
		close();
	}

	private boolean hasFilesToTake() {
		return takeQueue.size() > 0 ? true : false;
	}

	public void close() {
		running = false;
		stopServiceDiscovery();
	}

	public void doRemove() {
		clientState = ClientState.LEAVING;
	}

	public void doEnter() {
		clientState = ClientState.ACTIVE;
	}
	
	public boolean hasFilesToUpload(){
		return uploadQueue.size() > 0 ? true : false;
	}
	
	public void addUpload(File theFile){
		uploadQueue.add(new UploadItem(theFile, theFile.getName()));
	}
	
	public void addUpload(File theFile, String theName) {
		uploadQueue.add(new UploadItem(theFile, theName));
		
	}
	
	public void takeContent(ContentItem theContentItem){
		takeQueue.add(theContentItem);
	}
	
	public Vector<ClientItem> clients() {
		return clients;
	}

	public boolean shouldEnter() {
		if (clientState == ClientState.ENTERING)
			return true;
		return false;
	}

	public boolean shouldLeave() {
		if (clientState == ClientState.LEAVING)
			return true;
		return false;
	}





	public String getUrlForContentItem(ContentItem theContentItem) {
		String url = serverUrl;
		
		if(theContentItem != null){
			url += "/" + theContentItem.getHashId();
		}
		
		return url;
	}



	
	private class UploadItem {
		public File file;
		public String filename;
		
		public UploadItem(File theFile, String theFileName){
			file = theFile;
			filename = theFileName;
		}
	}








}
